-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mesh creation from primitive shapes #11007
Conversation
This is required for cone shading, but probably breaks mikktspace normal mapping. This should be done properly in another PR; don't merge this change!
It looks like your PR is a breaking change, but you didn't provide a migration guide. Could you add some context on what users should update when this change get released in a new version of Bevy? |
738bfea
to
a0c42ce
Compare
# Objective The first part of #10569, split up from #11007. The goal is to implement meshing support for Bevy's new geometric primitives, starting with 2D primitives. 3D meshing will be added in a follow-up, and we can consider removing the old mesh shapes completely. ## Solution Add a `Meshable` trait that primitives need to implement to support meshing, as suggested by the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/12-primitive-shapes.md#meshing). ```rust /// A trait for shapes that can be turned into a [`Mesh`]. pub trait Meshable { /// The output of [`Self::mesh`]. This can either be a [`Mesh`] /// or a builder used for creating a [`Mesh`]. type Output; /// Creates a [`Mesh`] for a shape. fn mesh(&self) -> Self::Output; } ``` This PR implements it for the following primitives: - `Circle` - `Ellipse` - `Rectangle` - `RegularPolygon` - `Triangle2d` The `mesh` method typically returns a builder-like struct such as `CircleMeshBuilder`. This is needed to support shape-specific configuration for things like mesh resolution or UV configuration: ```rust meshes.add(Circle { radius: 0.5 }.mesh().resolution(64)); ``` Note that if no configuration is needed, you can even skip calling `mesh` because `From<MyPrimitive>` is implemented for `Mesh`: ```rust meshes.add(Circle { radius: 0.5 }); ``` I also updated the `2d_shapes` example to use primitives, and tweaked the colors to have better contrast against the dark background. Before: ![Old 2D shapes](https://github.com/bevyengine/bevy/assets/57632562/f1d8c2d5-55be-495f-8ed4-5890154b81ca) After: ![New 2D shapes](https://github.com/bevyengine/bevy/assets/57632562/f166c013-34b8-4752-800a-5517b284d978) Here you can see the UVs and different facing directions: (taken from #11007, so excuse the 3D primitives at the bottom left) ![UVs and facing directions](https://github.com/bevyengine/bevy/assets/57632562/eaf0be4e-187d-4b6d-8fb8-c996ba295a8a) --- ## Changelog - Added `bevy_render::mesh::primitives` module - Added `Meshable` trait and implemented it for: - `Circle` - `Ellipse` - `Rectangle` - `RegularPolygon` - `Triangle2d` - Implemented `Default` and `Copy` for several 2D primitives - Updated `2d_shapes` example to use primitives - Tweaked colors in `2d_shapes` example to have better contrast against the (new-ish) dark background --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
…ine#11431) # Objective The first part of bevyengine#10569, split up from bevyengine#11007. The goal is to implement meshing support for Bevy's new geometric primitives, starting with 2D primitives. 3D meshing will be added in a follow-up, and we can consider removing the old mesh shapes completely. ## Solution Add a `Meshable` trait that primitives need to implement to support meshing, as suggested by the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/12-primitive-shapes.md#meshing). ```rust /// A trait for shapes that can be turned into a [`Mesh`]. pub trait Meshable { /// The output of [`Self::mesh`]. This can either be a [`Mesh`] /// or a builder used for creating a [`Mesh`]. type Output; /// Creates a [`Mesh`] for a shape. fn mesh(&self) -> Self::Output; } ``` This PR implements it for the following primitives: - `Circle` - `Ellipse` - `Rectangle` - `RegularPolygon` - `Triangle2d` The `mesh` method typically returns a builder-like struct such as `CircleMeshBuilder`. This is needed to support shape-specific configuration for things like mesh resolution or UV configuration: ```rust meshes.add(Circle { radius: 0.5 }.mesh().resolution(64)); ``` Note that if no configuration is needed, you can even skip calling `mesh` because `From<MyPrimitive>` is implemented for `Mesh`: ```rust meshes.add(Circle { radius: 0.5 }); ``` I also updated the `2d_shapes` example to use primitives, and tweaked the colors to have better contrast against the dark background. Before: ![Old 2D shapes](https://github.com/bevyengine/bevy/assets/57632562/f1d8c2d5-55be-495f-8ed4-5890154b81ca) After: ![New 2D shapes](https://github.com/bevyengine/bevy/assets/57632562/f166c013-34b8-4752-800a-5517b284d978) Here you can see the UVs and different facing directions: (taken from bevyengine#11007, so excuse the 3D primitives at the bottom left) ![UVs and facing directions](https://github.com/bevyengine/bevy/assets/57632562/eaf0be4e-187d-4b6d-8fb8-c996ba295a8a) --- ## Changelog - Added `bevy_render::mesh::primitives` module - Added `Meshable` trait and implemented it for: - `Circle` - `Ellipse` - `Rectangle` - `RegularPolygon` - `Triangle2d` - Implemented `Default` and `Copy` for several 2D primitives - Updated `2d_shapes` example to use primitives - Tweaked colors in `2d_shapes` example to have better contrast against the (new-ish) dark background --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective Split up from #11007, fixing most of the remaining work for #10569. Implement `Meshable` for `Cuboid`, `Sphere`, `Cylinder`, `Capsule`, `Torus`, and `Plane3d`. This covers all shapes that Bevy has mesh structs for in `bevy_render::mesh::shapes`. `Cone` and `ConicalFrustum` are new shapes, so I can add them in a follow-up, or I could just add them here directly if that's preferrable. ## Solution Implement `Meshable` for `Cuboid`, `Sphere`, `Cylinder`, `Capsule`, `Torus`, and `Plane3d`. The logic is mostly just a copy of the the existing `bevy_render` shapes, but `Plane3d` has a configurable surface normal that affects the orientation. Some property names have also been changed to be more consistent. The default values differ from the old shapes to make them a bit more logical: - Spheres now have a radius of 0.5 instead of 1.0. The default capsule is equivalent to the default cylinder with the sphere's halves glued on. - The inner and outer radius of the torus are now 0.5 and 1.0 instead of 0.5 and 1.5 (i.e. the new minor and major radii are 0.25 and 0.75). It's double the width of the default cuboid, half of its height, and the default sphere matches the size of the hole. - `Cuboid` is 1x1x1 by default unlike the dreaded `Box` which is 2x1x1. Before, with "old" shapes: ![old](https://github.com/bevyengine/bevy/assets/57632562/733f3dda-258c-4491-8152-9829e056a1a3) Now, with primitive meshing: ![new](https://github.com/bevyengine/bevy/assets/57632562/5a1af14f-bb98-401d-82cf-de8072fea4ec) I only changed the `3d_shapes` example to use primitives for now. I can change them all in this PR or a follow-up though, whichever way is preferrable. ### Sphere API Spheres have had separate `Icosphere` and `UVSphere` structs, but with primitives we only have one `Sphere`. We need to handle this with builders: ```rust // Existing structs let ico = Mesh::try_from(Icophere::default()).unwrap(); let uv = Mesh::from(UVSphere::default()); // Primitives let ico = Sphere::default().mesh().ico(5).unwrap(); let uv = Sphere::default().mesh().uv(32, 18); ``` We could add methods on `Sphere` directly to skip calling `.mesh()`. I also added a `SphereKind` enum that can be used with the `kind` method: ```rust let ico = Sphere::default() .mesh() .kind(SphereKind::Ico { subdivisions: 8 }) .build(); ``` The default mesh for a `Sphere` is an icosphere with 5 subdivisions (like the default `Icosphere`). --- ## Changelog - Implement `Meshable` and `Default` for `Cuboid`, `Sphere`, `Cylinder`, `Capsule`, `Torus`, and `Plane3d` - Use primitives in `3d_shapes` example --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Closing in favor of #11688 and the dozen other primitive meshing PRs that have already been merged ages ago :P |
Objective
The goal is to implement
Mesh
creation for the recently added primitive shapes (#10572), superseding the old set of shapes inbevy_render::mesh::shape
. The approach includes aMeshable
trait as suggested by the original RFC.As we rework the mesh shapes, we also have an opportunity to reach some secundary but important goals:
#![warn(missing_docs)]
clause.Closes #10569 (although this might be done across several PRs)
Solution
There is now a
Meshable
trait:It's implemented for all of the primitive shapes that support meshing, i.e. currently all of these:
Capsule
Circle
Cone
ConicalFrustum
Cuboid
Cylinder
Ellipse
Plane3d
Rectangle
RegularPolygon
Sphere
Torus
Triangle2d
Each shape has its own
FooMesh
version that is returned by themesh
method. These separate structs are required for configuration like specifying the resolution or UV profile, and it uses a builder-like API for ease of use.For example, the
CapsuleMesh
, which looks like the currentCapsule
mesh struct, except it uses theCapsule
primitive:The
Mesh
postfix is used for clarity and to avoid naming conflicts, but it could beCapsuleBuilder
orCapsuleMeshBuilder
too.It has constructors and builder methods to make configuration easier:
Using it looks like this:
Note that
build
can even be omitted when adding meshes likemeshes.add(...)
if #10878 gets merged.From<Capsule>
andFrom<CapsuleMesh>
are also implemented forMesh
, sointo
would work as well.2D meshes now also support different orientations with the
Facing
enum to face any of the three cartesian axes:This is similar to Godot's facing direction for quads and also doubles as a way to flip meshes. Another reason why this is useful is that we can use it to refactor internal mesh logic to be more reusable. For example, we can use
CircleMesh
for constructing the top and bottom of aCylinderMesh
, but just flip the direction the circle is facing.Below are a few screenshots showing all of the supported shapes with different orientations for 2D shapes.
With UV visualization:
With textures:
With wireframes:
The implementation details of each mesh can be outlined in more detail in their own PRs if I split this PR into granular parts.
Missing mesh implementations
Lines and
Direction2d
/Direction3d
currently don't support meshing because they would require wireframe materials to be rendered (unless we model lines as e.g. capsules or cylinders with thickness) and it's generally better done with gizmos.Polygon
doesn't support meshing because it requires triangulation, which is complex to do well and would most likely require external dependencies. I've tried using earcutr, geo, and lyon_tessellation for this, but none of them seem to support both self-intersections and boolean operations at the same time correctly.Meshes for these can be implemented later.
API alternatives
The API I opted for is builder-like and has separate structs for the mesh versions of shapes. Some other options I considered include:
Meshable
trait or separate structs, just implement mesh creation for each shape separately likeSphere::new(0.5).uv(longitudes, latitudes)
Mesh::from(Sphere::new(0.5))
Compare this to the builder API:
Sphere::new(0.5).mesh().uv(longitudes, latitudes)
orSphereMesh::new(radius, SphereKind::Uv { longitudes, latitudes }).build()
build
can even be omitted when adding meshes likemeshes.add(...)
if Useimpl Into<A>
forAssets::add
#10878 gets mergedI believe the builder-like API is the ideal middle-ground between simplicity, ease of use, and configurability, and I can't think of many major downsides. Feel free to leave suggestions though!
Open questions
primitives::Sphere::new(0.5).mesh().uv(longitudes, latitudes)
andshapes::SphereMesh::new(radius, SphereKind::Uv { longitudes, latitudes }).build()
. Do we want to keep theMeshable
API and have these coexist, or should we perhaps remove it? And which approach should we recommend and use in examples?CuboidMesh
for the sake of consistency? Personally, I think they should.Next steps
If this approach is approved, I can submit granular PRs to add all of the functionality in this PR in chunks that are easier to review independently. The PRs might be roughly like this:
Meshable
trait and mesh creation for 2D primitivesMesh
creation forSphere
,Cylinder
,Capsule
, andTorus
(the new versions of the "old" 3D shapes)Mesh
creation forCone
andConicalFrustum
Mesh
creation forPlane3d
Once all of this is done, we should implement meshing for the remaining primitives, add similar support for gizmos (#10571) and finish other remaining items in #10572.